﻿using OperationPlugins.Compositions;
using System;
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using System.Web;

namespace OperationPlugins
{
    public static class OperationContextManager
    {
        private const string ContextKey = "OperationContextManager";

        private static IOperationContextFactory _globalFactory;
        private static IOperationContextFactory _factory;

        /// <summary>
        /// Gets the IOperationContextFactory.
        /// </summary>
        /// <returns>IOperationContextFactory</returns>
        public static IOperationContextFactory GetOperationContextFactory()
        {
            return _globalFactory ?? _factory;
        }

        /// <summary>
        /// Sets the IOperationContextFactory to be used.
        /// </summary>
        /// <param name="contextFactory">The IOperationContextFactory to be used.</param>
        public static void SetOperationContextFactory(IOperationContextFactory contextFactory)
        {
            if (contextFactory == null)
            {
                throw new ArgumentNullException("contextFactory");
            }

            _globalFactory = contextFactory;
        }

        /// <summary>
        /// Tells the framework that it should use the default IOperationContextFactory.
        /// </summary>
        public static void UseDefaultOperationContextFactory()
        {
            _globalFactory = null;
        }

        internal static OperationContext GetOperationContext(OperationId operation, BranchId branch, IOperationContextFactory contextFactory)
        {
            if (operation == null)
            {
                throw new ArgumentNullException("operation");
            }

            if (branch == null)
            {
                throw new ArgumentNullException("branch");
            }

            if (contextFactory == null)
            {
                throw new ArgumentNullException("contextFactory");
            }

            HttpContext http = HttpContext.Current;

            if (http == null)
            {
                throw new OperationContextManagerException("The HttpContext instance for the current HTTP request is not available.");
            }

            ConcurrentDictionary<string, OperationContext> cache = http.Items[ContextKey] as ConcurrentDictionary<string, OperationContext>;

            if (cache == null)
            {
                http.Items[ContextKey] = cache = new ConcurrentDictionary<string, OperationContext>();
            }

            string key = string.Format("{0}|{1}", operation.GetHashCode(), branch.GetHashCode());

            OperationContext context;

            if (cache.TryGetValue(key, out context) == false)
            {
                context = contextFactory.CreateOperationContext(operation, branch);

                if (cache.TryAdd(key, context) == false)
                {
                    throw new OperationContextManagerException("Failed to cache the OperationContext.");                    
                }
            }

            return context;
        }

        /// <summary>
        /// Initialize. 
        /// </summary>
        [Export(typeof(IInitializable))]
        private sealed class Initializer : IInitializable
        {
            void IInitializable.Initialize(InitializationContext context)
            {
                _factory = context.CompositionContainer.GetPlugin<IOperationContextFactory>();
            }
        }

        /// <summary>
        /// The default operation context factory. 
        /// </summary>
        [Export(typeof(IOperationContextFactory))]
        [DefaultPriority]
        private sealed class DefaultOperationContextFactory : IOperationContextFactory
        {
            OperationContext IOperationContextFactory.CreateOperationContext(OperationId operation, BranchId branch)
            {
                if (operation == null)
                {
                    throw new ArgumentNullException("operation");
                }

                if (branch == null)
                {
                    throw new ArgumentNullException("branch");
                }

                return new OperationContext { Operation = operation, Branch = branch };
            }
        }

        /// <summary>
        /// The exception that is thrown when a fatal error occurs inside OperationContextManager.
        /// </summary>
        public class OperationContextManagerException : Exception
        {
            public OperationContextManagerException(string message)
                : base(message)
            {
            }
        }

    }
}
